﻿// Note: this is as obsolete as possible. See MPExtended for continuation of development. 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Web;
using System.Threading;
using System.Xml;
using MPWebStream.MediaTranscoding;
using TV4Home.Server.TVEInteractionLibrary.Interfaces;
using TvControl;

namespace TV4Home.Server.TVEInteractionLibrary
{
    internal class Streaming
    {
        /// <summary>
        /// WCF recreates our object on every request, while we need some way to preserve objects between requests: the
        /// streamer process needs to be available on the status methods and on the end methods. This is a very ugly
        /// implementation that allows us to keep some data available in between requests.
        /// </summary>
        private static class PersistentHolder
        {
            public static Dictionary<string, ActiveStream> Streams = new Dictionary<string, ActiveStream>();
            public static Dictionary<string, WebMediaInfo> MediaInfoCache = new Dictionary<string, WebMediaInfo>();
        }

        private class ActiveStream
        {
            public string Identifier { get; set; }
            public string Source { get; set; }
            public int StartPosition { get; set; }
            public FullTranscoderProfile Profile { get; set; }
            public TranscodingStreamer Transcoder { get; set; }
            public Stream OutputStream { get; set; }
            public Thread ParseOutputThread { get; set; }
            public FFmpegEncodingInfo TranscodingInfo;
        }

        public class FullTranscoderProfile : TranscoderProfile
        {
            public string Description { get; set; }
            public string Bandwidth { get; set; }
            public string Target { get; set; }
            public string CodecParameters { get; set; }
            public string MaxOutputSize { get; set; }

            public FullTranscoderProfile(TranscoderProfile profile)
            {
                // I miss some automatic way of downcasting here
                this.InputMethod = profile.InputMethod;
                this.MIME = profile.MIME;
                this.Name = profile.Name;
                this.OutputMethod = profile.OutputMethod;
                this.Parameters = profile.Parameters;
                this.Transcoder = profile.Transcoder;
                this.UseTranscoding = profile.UseTranscoding;
            }

            public WebTranscoderProfile ToWebTranscoderProfile()
            {
                return new WebTranscoderProfile()
                {
                    Name = Name,
                    MIME = MIME,
                    Description = Description,
                    Bandwidth = Bandwidth,
                    UseTranscoding = UseTranscoding,
                    Target = Target,
                    MaxOutputSize = MaxOutputSize
                };
            }
        }

        public bool InitStream(string identifier, VirtualCard card, FullTranscoderProfile profile)
        {
            return DoInitStream(new ActiveStream()
            {
                Identifier = identifier,
                Profile = profile,
                Source = card.TimeShiftFileName, // MPWebStream's special TV constructor is buggy
                StartPosition = 0 // seeking in TV streams is not supported (yet?)
            });
        }

        public bool InitStream(string identifier, string source, FullTranscoderProfile profile, int startPosition)
        {
            return DoInitStream(new ActiveStream()
            {
                Identifier = identifier,
                Profile = profile,
                Source = source,
                StartPosition = startPosition
            });
        }

        private bool DoInitStream(ActiveStream stream)
        {
            try
            {
                // build ffmpeg command line
                string start = stream.StartPosition > 0 ? "-ss " + stream.StartPosition.ToString() : "";
                stream.Profile.Parameters = "-y " + start + " -i \"{0}\" -s " + stream.Profile.MaxOutputSize + " -aspect 16:9 " + stream.Profile.CodecParameters + " \"{1}\"";

                // start transcoding
                stream.Transcoder = new TranscodingStreamer(stream.Source, stream.Profile);
                stream.Transcoder.WantLogStream = true;
                stream.Transcoder.StartTranscoding();

                // setup output parsing (don't start yet as the stream isn't in PersistentHolder yet)
                stream.ParseOutputThread = new Thread(new ParameterizedThreadStart(delegate(object ident)
                {
                    ActiveStream astream = PersistentHolder.Streams[(string)ident];
                    FFmpegUtil.ParseOutputStream(astream.Transcoder.GetTranscoderLogStream(), out PersistentHolder.Streams[(string)ident].TranscodingInfo, true, true);
                }));

                // save the stream
                PersistentHolder.Streams[stream.Identifier] = stream;

                // wait for stream to be ready
                stream.OutputStream = stream.Transcoder.StartStream();
                stream.ParseOutputThread.Start(stream.Identifier);
                Log.Debug("Waiting for output stream to be ready");
                while (!stream.OutputStream.CanRead)
                    System.Threading.Thread.Sleep(50);

                Log.Debug("DoInitStream succeeded");
                return true;
            }
            catch (Exception e)
            {
                Log.Error("Failed to init stream: " + e.Message, e);
                // not sure if we have correct NLog configuration here
                Log.Warn("Stacktrace as string: " + e.StackTrace.ToString());
                return false;
            }
        }

        public Stream RetrieveStream(string identifier)
        {

            if (PersistentHolder.Streams.ContainsKey(identifier))
            {
                WebOperationContext.Current.OutgoingResponse.ContentType = PersistentHolder.Streams[identifier].Profile.MIME;
                return PersistentHolder.Streams[identifier].OutputStream;
            }
            return null;
        }

        public void EndStream(string identifier)
        {
            if (!PersistentHolder.Streams.ContainsKey(identifier))
                return;
            PersistentHolder.Streams[identifier].Transcoder.StopTranscoding();
            PersistentHolder.Streams.Remove(identifier);
        }

        public WebTranscodingInfo GetTranscodingInfo(string identifier)
        {
            return PersistentHolder.Streams[identifier].TranscodingInfo.ToWebTranscodingInfo();
        }

        #region MediaInfo
        public WebMediaInfo GetMediaInfo(string source)
        {
            // first check for cache hit
            if (PersistentHolder.MediaInfoCache.ContainsKey(source))
                return PersistentHolder.MediaInfoCache[source];

            if (!File.Exists(source))
            {
                Log.Warn("GetMediaInfo: File " + source + " doesn't exist or is not accessible");
                return null;
            }

            /* Loosely based upon MediaInfoWrapper.cs (mediaportal/Core/Player) from MediaPortal trunk r27491 as of 15 June 2011
             * 
             * Using the whole wrapper from MediaPortal is quite much porting work as it's cluttered with calls to other MP code. Referencing
             * MediaPortal.Core.dll also isn't an option as that pulls in a big tree of dependencies. 
             * 
             * TODO: Needs error handling
             * TODO: No support for DVDs yet
             * TODO: Aspect ratio doesn't work properly yet
             */
            MediaInfo info = new MediaInfo();
            info.Option("ParseSpeed", "0.3");
            info.Open(source);

            // why can't properties be passed as out parameters?
            long duration;
            Int64.TryParse(info.Get(StreamKind.Video, 0, "Duration"), out duration);
            string aspectString = info.Get(StreamKind.Video, 0, "DisplayAspectRatio/String");
            string strAspect = info.Get(StreamKind.Video, 0, "DisplayAspectRatio");
            decimal aspect = 0;
            if (strAspect != null && !strAspect.Equals(""))
            {
                aspect = Decimal.Parse(strAspect, System.Globalization.CultureInfo.InvariantCulture);
            }
            string codec = info.Get(StreamKind.Video, 0, "Codec/String");

            info.Close();

            // save it in cache
            WebMediaInfo ret = new WebMediaInfo()
            {
                Duration = duration,
                DisplayAspectRatioString = aspectString,
                DisplayAspectRatio = aspect,
                SourceCodec = codec,
            };
            PersistentHolder.MediaInfoCache[source] = ret;
            return ret;
        }
        #endregion

        #region Profiles
        public FullTranscoderProfile GetTranscoderProfileByName(string name)
        {
            return GetTranscoderProfiles().Where(s => s.Name == name).FirstOrDefault();
        }

        public List<FullTranscoderProfile> GetTranscoderProfiles()
        {
            List<FullTranscoderProfile> list = new List<FullTranscoderProfile>();
            XmlDocument doc = new XmlDocument();
            doc.Load(AppDomain.CurrentDomain.BaseDirectory + "TranscoderConfig.xml");
            XmlNodeList nodes = doc.SelectNodes("/appconfig/transcoders/transcoder");
            foreach (XmlNode node in nodes)
            {
                FullTranscoderProfile profile = new FullTranscoderProfile(TranscoderProfile.CreateFromXmlNode(node));
                profile.Description = node.ChildNodes.Cast<XmlNode>().Where(n => n.Name == "description").FirstOrDefault().InnerText;
                profile.Target = node.ChildNodes.Cast<XmlNode>().Where(n => n.Name == "target").FirstOrDefault().InnerText;
                profile.Bandwidth = node.ChildNodes.Cast<XmlNode>().Where(n => n.Name == "bandwidth").FirstOrDefault().InnerText;
                profile.CodecParameters = node.ChildNodes.Cast<XmlNode>().Where(n => n.Name == "codecParameters").FirstOrDefault().InnerText;
                profile.MaxOutputSize = node.ChildNodes.Cast<XmlNode>().Where(n => n.Name == "maxOutputSize").FirstOrDefault().InnerText;
                list.Add(profile);
            }
            return list;
        }
        #endregion
    }
}
